/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 */

import cluster from 'cluster';
import path from 'path';
import process from 'process';

import { ErrorUtil } from '@ohos/hvigor-logger';

import { HookType } from '../../../common/const/hook-const.js';
import { HvigorCommonConst } from '../../common/options/common-const.js';
import { addConfigDeps, addModuleDependency } from '../../daemon/cluster/watch-config-file.js';
import { HvigorCoreNode, Project } from '../../external/core/hvigor-core-node.js';
import { hvigorCore } from '../../external/core/hvigor-core.js';
import { hvigor } from '../../external/core/hvigor.js';
import { HvigorSystemPlugin } from '../../external/plugin/hvigor-system-plugin.js';
import { HvigorLogger } from '../../log/hvigor-log.js';
import { DurationEvent } from '../../metrics/event/duration-event.js';
import { MetricLogType } from '../../metrics/event/log-event.js';
import { findRealHvigorFilePath } from '../../util/file-util.js';
import { formatTime } from '../../util/time-util.js';
import { HvigorNode } from '../../vigor/plugin/interface/hvigor-node.js';
import { HvigorPlugin } from '../../vigor/plugin/interface/hvigor-plugin.js';
import { LoaderProfile } from '../../vigor/plugin/interface/loader-profile.js';
import { coreParameter } from '../data/global-core-parameters.js';
import { globalData } from '../data/global-data.js';
import { TaskTree } from '../task/build/task-tree.js';
import { Tasks } from '../task/build/tasks.js';
import { PruneTask } from '../task/prune/task-prune.js';
import { hvigorfileTSRoots } from '../util/ts_check.js';
import { HvigorLifecycleHook } from './hook/hvigor-lifecycle-hook.js';

const _log = HvigorLogger.getLogger('configuration');

/**
 * Config整个hvigor的项目，进行数据绑定
 *
 */
export async function configuration(parentEvent: DurationEvent): Promise<HvigorCoreNode> {
  const initConfigurationEventName = 'init configuration';
  const configProjectTaskEventName = 'configure project task';
  const evalProjectEventName = 'eval project';
  const evalModulesEventName = 'eval modules';
  const addConfigDepsEventName = 'add config dependencies';
  const ohpmInstallEventName = 'ohpm install';
  const evalHookEventName = 'eval hook';
  const finishConfigurationEventName = 'fin configuration';
  const initConfigurationEvent = parentEvent.createSubEvent(initConfigurationEventName, 'Initialize configuration.');
  const configProjectTaskEvent = parentEvent.createSubEvent(configProjectTaskEventName, 'Configure project task.');
  const evalProjectEvent = parentEvent.createSubEvent(evalProjectEventName, 'Evaluate project.');
  const evalModulesEvent = parentEvent.createSubEvent(evalModulesEventName, 'Evaluate modules.');
  const addConfigDepsEvent = parentEvent.createSubEvent(addConfigDepsEventName, 'Add configuration dependencies.');
  const ohpmInstallEvent = parentEvent.createSubEvent(ohpmInstallEventName, 'Ohpm install event.');
  const evalHookEvent = parentEvent.createSubEvent(evalHookEventName, 'EvaluateEvent hook.');
  const finishConfigurationEvent = parentEvent.createSubEvent(finishConfigurationEventName, 'Finish configuration.');

  initConfigurationEvent.start();
  const configurationBeginTime = process.hrtime();
  const env = globalData.cliEnv;
  const project = hvigorCore.getProject()!;
  const projectVigorFile = path.resolve(env.cwd, HvigorCommonConst.BUILD_FILE_NAME);
  initConfigurationEvent.stop().setLog(initConfigurationEventName, MetricLogType.INFO);

  // 目前hvigor解析node的时候插件加载和apply不在一个时机 需要讨论一次 到底改在一起还是保持不在一起
  // 先触发所有的beforeNodeEvaluate Hook然后把project和所有的Module evaluate并且执行完 然后挨个调用after的hook

  // 先evaluate project
  configProject(project, configProjectTaskEvent);
  await evalProject(project, projectVigorFile, evalProjectEvent);

  // 再evaluate所有子Module
  await evalSubModules(project, evalModulesEvent);

  addConfigDependencies(addConfigDepsEvent);

  // 触发nodesEvaluated hook 其中runHook本身有打点
  await HvigorLifecycleHook.getInstance().runHook(HookType.nodesEvaluated, hvigor, parentEvent);
  // 执行ohpm install 安装
  ohpmInstallEvent.start();
  await HvigorLifecycleHook.getInstance().runFn(hvigorCore.getHvigorNodesBeforeEvaluatedInternalHook(), hvigor);
  ohpmInstallEvent.stop().setLog(ohpmInstallEventName, MetricLogType.INFO);
  // 触发内部的钩子，保证系统插件的依赖关系先与用户的钩子准备好
  evalHookEvent.start();
  await HvigorLifecycleHook.getInstance().runFn(hvigorCore.getHvigorNodesEvaluatedInternalHook(), hvigor);
  evalHookEvent.stop().setLog(evalHookEventName, MetricLogType.INFO);

  finishConfigurationEvent.start();
  const taskEndTime: [number, number] = process.hrtime(configurationBeginTime);
  const realTime = formatTime(taskEndTime);
  _log.debug(`Configuration phase cost:${realTime}`);
  finishConfigurationEvent.stop().setLog(finishConfigurationEventName, MetricLogType.INFO);
  return project;
}

function configProject(project: Project, configProjectTaskEvent: DurationEvent) {
  configProjectTaskEvent.start();
  configNodeTask(project);
  configProjectTask(project);
  configProjectTaskEvent.stop().setLog();
}

async function evalProject(project: Project, projectVigorFile: string, evalProjectEvent: DurationEvent) {
  evalProjectEvent.start();

  // 执行前后触发project上的hook  先触发hvigor上注册的对全部node的hook 再触发每个node上注册的单独的node
  const hvigorNode = hvigor.getNodeByName(project.getName());
  hvigorNode !== undefined && (await HvigorLifecycleHook.getInstance().runHook(HookType.beforeNodeEvaluate, hvigorNode, evalProjectEvent));
  await project.executeNodeHook(HookType.beforeNodeEvaluate);

  await evaluateNodeVigorFile(project, findRealHvigorFilePath(projectVigorFile, _log)!, evalProjectEvent);

  // 由于系统插件的apply和getContext方法不是异步的 所以sdk加载不能再在evaluateNodeVigorFile执行时 只能注册个hook来加载sdk
  // 这里给一个内部的hook 执行顺序在最前面 给内部用
  const hookList = hvigorCore.getHvigorAfterNodeInternalHookList(project.getName());
  try {
    hvigorNode !== undefined && hookList !== undefined && (await HvigorLifecycleHook.getInstance().runFn(hookList, hvigorNode));
    hvigorNode !== undefined && (await HvigorLifecycleHook.getInstance().runHook(HookType.afterNodeEvaluate, hvigorNode, evalProjectEvent));
    await project.executeNodeHook(HookType.afterNodeEvaluate);
  } catch (e: any) {
    if (ErrorUtil.isAdaptorError(e)) {
      throw e;
    }
    _log.printErrorExit('CATCH_ERROR_IN_HVIGOR_FILE',
      [e.name.toString().replace('hvigor_1.', ''),
        e.message.toString().replace('hvigor_1.', ''),
        `${projectVigorFile.toString().replace('hvigor_1.', '')}.ts`]);
  }
  evalProjectEvent.stop().setLog();
}

async function evalSubModules(project: Project, evalModulesEvent: DurationEvent) {
  evalModulesEvent.start();
  for (const subModule of project.getAllSubModules()) {
    const evalSubModuleEventName = 'eval submodule';
    const evalSubModuleEvent = evalModulesEvent.createSubEvent(evalSubModuleEventName, 'Evaluate submodule.');
    evalSubModuleEvent.start();
    configNodeTask(subModule);
    // 执行前后触发subModule上的hook 先触发hvigor上注册的对全部node的hook 再触发每个node上注册的单独的node
    const hvigorNode = hvigor.getNodeByName(subModule.getName());
    hvigorNode !== undefined && (await HvigorLifecycleHook.getInstance().runHook(HookType.beforeNodeEvaluate, hvigorNode, evalSubModuleEvent));
    await subModule.executeNodeHook(HookType.beforeNodeEvaluate);

    await evaluateNodeVigorFile(subModule, findRealHvigorFilePath(subModule.getBuildFilePath(), _log)!, evalSubModuleEvent);

    // 由于系统插件的apply和getContext方法不是异步的 所以sdk加载不能再在evaluateNodeVigorFile执行时 只能注册个hook来加载sdk
    // 这里给一个内部的hook 执行顺序在最前面 给内部用
    const hookList = hvigorCore.getHvigorAfterNodeInternalHookList(subModule.getName());
    try {
      hvigorNode !== undefined && hookList !== undefined && (await HvigorLifecycleHook.getInstance().runFn(hookList, hvigorNode));
      hvigorNode !== undefined && (await HvigorLifecycleHook.getInstance().runHook(HookType.afterNodeEvaluate, hvigorNode, evalSubModuleEvent));
      await subModule.executeNodeHook(HookType.afterNodeEvaluate);
    } catch (e: any) {
      if (ErrorUtil.isAdaptorError(e)) {
        throw e;
      }
      _log.printErrorExit('CATCH_ERROR_IN_HVIGOR_FILE',
        [e.name.toString().replace('hvigor_1.', ''),
          e.message.toString().replace('hvigor_1.', ''),
          path.resolve(<string>hvigorNode?.getNodePath(), 'hvigorfile.ts').toString().replace('hvigor_1.', '')]);
    }
    evalSubModuleEvent.stop().setLog(evalSubModuleEventName, MetricLogType.INFO);
  }
  evalModulesEvent.stop().setLog();
}

function addConfigDependencies(addConfigDepsEvent: DurationEvent) {
  addConfigDepsEvent.start();
  if (cluster.isWorker) {
    const startTime = process.hrtime();
    addConfigDeps();
    const taskEndTime: [number, number] = process.hrtime(startTime);
    const realTime = formatTime(taskEndTime);
    _log.debug(`hvigorfile, resolve hvigorfile dependencies in ${realTime}`);
  }
  addConfigDepsEvent.stop().setLog();
}

function configNodeTask(node: HvigorCoreNode): void {
  node.registry(new Tasks(node));
  node.registry(new TaskTree(node));
}

// 按照项目注册 , 只执行一次
function configProjectTask(node: HvigorCoreNode): void {
  node.registry(new PruneTask(node));
}

/**
 * 解析hvigorfile中的系统插件，兼容不同历史不同版本的写法
 * 1. module.exports=require('@ohos/hvigor-ohos-plugin').xxxTasks
 * 2. module.exports=require('@ohos/hvigor-ohos-plugin').xxxTasks(xxx)
 * 3. module.exports={
 *       xxx:xxx
 *    }
 */
async function bindSystemPlugins(hvigorFileExports: any, node: HvigorCoreNode, hvigorFilePath: string) {
  _log.debug('hvigorfile, binding system plugins', hvigorFileExports);
  if (typeof hvigorFileExports === 'function') {
    const pluginExports = await hvigorFileExports(node);

    node.bindPlugin(pluginExports);
    node.bindPluginContextFunc(pluginExports.getPluginId(), pluginExports.getContext.bind(pluginExports));
    return;
  }

  // 直接module.exports 一个plugin
  if (hvigorFileExports instanceof HvigorSystemPlugin) {
    return;
  }

  if (!Object.keys(hvigorFileExports).length) {
    _log.printErrorExit('NO_SYSTEM_PLUGINS_WERE_FOUND_IN_HVIGORFILE', [hvigorFilePath]);
  }

  for (const key of Object.keys(hvigorFileExports)) {
    const exportValue = hvigorFileExports[key];
    if (exportValue instanceof HvigorSystemPlugin) {
      node.bindPlugin(exportValue);
    } else if (typeof exportValue === 'function') {
      const pluginExports = await exportValue(node);
      if (!pluginExports.getPluginId) {
        _log.printErrorExit('NO_SYSTEM_PLUGINS_WERE_FOUND_IN_HVIGORFILE', [hvigorFilePath]);
      }
      node.bindPlugin(pluginExports);
    }
  }
}

async function bindCustomPlugins(customPlugins: HvigorPlugin[], node: HvigorCoreNode, hvigorFilePath: string, parentEvent: DurationEvent) {
  _log.debug('hvigorfile, binding custom plugins', customPlugins);

  // 用户没有配置自定义插件，属于正常场景
  if (!customPlugins || (Array.isArray(customPlugins) && customPlugins.length === 0)) {
    _log.debug(`hvigorfile, no custom plugins were found in ${hvigorFilePath}`);
    return;
  }

  // 用户配置了自定义插件，但是格式不对，属于异常场景
  if (!Array.isArray(customPlugins)) {
    _log.warn(`hvigorfile, invalid custom plugins config found in ${hvigorFilePath}`);
    return;
  }

  const pluginContext: HvigorNode = hvigorCore.getHvigorNodeByScriptPath(hvigorFilePath!.substring(0, hvigorFilePath.lastIndexOf('.')))!;

  for (let index = 0; index < customPlugins.length; index++) {
    const customPlugin = customPlugins[index];
    if (typeof customPlugin !== 'object' || typeof customPlugin.apply !== 'function') {
      _log.warn(
        `The plugin 'plugins[${index}]' in the file '${hvigorFilePath}' is invalid. ` +
          'This plugin will not be loaded. A valid plugin must contain a ' +
          '\'pluginId\' with string type and an \'apply\' function',
      );
      continue;
    }

    if (customPlugin.context) {
      node.bindPluginContextFunc(customPlugin.pluginId, customPlugin.context.bind(customPlugin));
    }

    if (customPlugin.apply) {
      hvigorCore.hvigorAfterNodeInternalHook(node.getName(), async () => {
        const applyCustomPluginEventName = 'apply custom plugin';
        const applyCustomPluginEvent = parentEvent.createSubEvent(applyCustomPluginEventName, `Apply custom plugin ${customPlugin.pluginId}.`);
        applyCustomPluginEvent.start();
        _log.debug(`hvigorfile, executing apply function of custom plugin, pluginId = ${customPlugin.pluginId}`);
        try {
          await customPlugin.apply(pluginContext);
        } catch (e: any) {
          _log.printErrorExit('FAILED_TO_EXECUTE_FUNCTION_%S_OF_THE_CUSTOM_PLUGIN',
            ['apply', customPlugin.pluginId, e.message],
            [['apply', customPlugin.pluginId]],
            e.stack
          );
        }
        applyCustomPluginEvent.stop().setLog(applyCustomPluginEventName, MetricLogType.INFO);
      });
    }
  }
}

async function evaluateNodeVigorFile(node: HvigorCoreNode, hvigorFilePath: string, parentEvent: DurationEvent): Promise<void> {
  const evalNodeVigorFileEventName = 'eval hvigorfile';
  const requireVigorFileEventName = 'require hvigorfile';
  const bindPluginsEventName = 'bind plugins';
  const evalNodeVigorFileEvent = parentEvent.createSubEvent(evalNodeVigorFileEventName, 'Evaluate hvigorfile.');
  const requireVigorFileEvent = evalNodeVigorFileEvent.createSubEvent(requireVigorFileEventName, 'Require hvigorfile.');
  const bindPluginsEvent = evalNodeVigorFileEvent.createSubEvent(bindPluginsEventName, 'Bind plugins.');
  evalNodeVigorFileEvent.start();
  requireVigorFileEvent.start();
  _log.debug(`hvigorfile, resolving ${hvigorFilePath}`);
  if (coreParameter.startParams.hvigorfileTypeCheck && hvigorFilePath.endsWith('.ts')) {
    hvigorfileTSRoots.add(hvigorFilePath);
  }

  delete require.cache[require.resolve(hvigorFilePath)];

  let hvigorFileExports;
  try {
    hvigorFileExports = require(hvigorFilePath);
    _log.debug('hvigorfile, require result: ', hvigorFileExports);
  } catch (e: any) {
    if (e instanceof ReferenceError && e.message === 'TransformStream is not defined') {
      _log.printErrorExit('NODE_VERSION_TOO_LOW', [e.message], undefined, e.stack);
    }
    _log.printErrorExit('FAILED_TO_REQUIRE_HVIGORFILE', [e.message, hvigorFilePath], undefined, e.stack);
  }
  requireVigorFileEvent.stop().setLog(requireVigorFileEventName, MetricLogType.INFO);

  bindPluginsEvent.start();

  const hvigorProfile = hvigorFileExports.default as LoaderProfile;

  // 适配任务扩展插件
  if (hvigorProfile && hvigorProfile.system) {
    // 解析hvigorfile.ts中config配置
    parseConfig(node, hvigorFileExports.default.config);

    await bindSystemPlugins(hvigorFileExports.default.system, node, hvigorFilePath);
    await node.executeAfterBindSystemPluginsHook();
    await bindCustomPlugins(hvigorFileExports.default.plugins, node, hvigorFilePath, bindPluginsEvent);
  } else {
    await bindSystemPlugins(hvigorFileExports, node, hvigorFilePath);
    await node.executeAfterBindSystemPluginsHook();
  }
  _log.debug(`hvigorfile, resolve finished ${hvigorFilePath}`);

  if (cluster.isWorker) {
    // 保证只在cluster场景下去做解析hvigorfile文件依赖的事情
    addModuleDependency(hvigorFilePath);
  }
  bindPluginsEvent.stop().setLog(bindPluginsEventName, MetricLogType.INFO);
  evalNodeVigorFileEvent.stop().setLog(evalNodeVigorFileEventName, MetricLogType.INFO);
}

function parseConfig(node: HvigorCoreNode, config: any) {
  if (!config) {
    return;
  }
  node.loadConfig(config);
}
